home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
system
/
admin
/
makedev-.5
/
makedev-
/
makedev-1.5
/
devices.c
next >
Wrap
C/C++ Source or Header
|
1995-03-25
|
21KB
|
712 lines
/*
* devices.c: Code for MAKEDEV, a program to create entries in /dev.
*
* Based on the MAKEDEV shell script, version 2.0, distributed with
* util-linux 1.10 and written by Nick Holloway.
*
* A number of bugs were fixed, and some additional features added.
* Written 10-Dec-94 by David A. Holland, dholland@husc.harvard.edu
* Rik Faith (faith@cs.unc.edu) contributed ideas and patches.
*
* Copyright 1994, 1995. All rights reserved.
* See the file LEGAL.NOTICE for conditions of redistribution.
*
* Bugs:
* None known right now.
*
* History:
*
* Version 2: 25-Mar-95 Fixed makefile.
* Look for config files in ".." under testing conditions.
* Big source split: makedev.syn -> parser.syn and devices.c.
* Consequently, this file's version numbers aren't the same as the
* whole program's any more.
* Version 1.4b: 25-Mar-95 Merged Rik's changes. Additional bug fixes:
* Don't leave off the last entry in a range.
* Parse hex digits correctly [sigh...].
* Now we actually *use* the ishex flag.
* Version 1.4a: 26-Feb-95 Forced devinfo and makedev.cfg to be in /etc.
* [from faith@cs.unc.edu]
* Version 1.4: 15-Jan-95 Wrote man pages. Now reads DEVINFO.local.
* Version 1.3: 31-Dec-94 Bug fixes. Added batches. Added omits.
* Version 1.2: 11-Dec-94 Add configuration file parsing.
* Version 1.1: 11-Dec-94 Distinguish block and character devices in the
* table of major device numbers. Changed the name and format of the
* update cache file to include the type. It appears that the old script
* was broken in this regard.
* Version 1.0: 10-Dec-94 Initial version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include "devices.h"
#include "version.h"
static int isverbose=NO; /* flag: print out what we do? */
static int deletion=NO; /* flag: delete instead of create */
static int donothing=NO; /* flag: don't actually do anything */
/*
* Roll over and die.
*/
void crash(const char *msg) {
fprintf(stderr, "MAKEDEV: %s\n", msg);
exit(1);
}
/*
* Print a warning.
*/
void warn(const char *format, ...) {
va_list ap;
va_start(ap, format);
fprintf(stderr, "MAKEDEV: ");
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
va_end(ap);
}
/*
* Translate string name to uid.
*/
static uid_t name2uid(const char *name) {
struct passwd *p = getpwnam(name);
if (!p) warn("undefined user: %s, using uid 0", name);
return p ? p->pw_uid : 0; /* make things owned by root by default */
}
/*
* Translate string name to gid.
*/
static gid_t name2gid(const char *name) {
struct group *g = getgrnam(name);
if (!g) warn("undefined group: %s, using gid 0", name);
return g ? g->gr_gid : 0; /* group 0 is a good default too */
}
/************************* device classes *************************/
/*
* A device class is a string attached to the device which tells us
* what set of permissions and ownership should be used. This is the
* table of classes.
*/
typedef struct {
const char *classname;
const char *owner;
const char *group;
int mode;
} devclass;
#define MAXCLASSES 32
static devclass classes[MAXCLASSES];
static int nclasses=0;
void addclass(const char *name, const char *o, const char *g, int m) {
if (nclasses>=MAXCLASSES) crash("out of space for device classes");
classes[nclasses].classname = name;
classes[nclasses].owner = o;
classes[nclasses].group = g;
classes[nclasses].mode = m;
nclasses++;
name2uid(o); /* check for undefined users/groups */
name2gid(g);
}
static void loadclasses(void) {
FILE *f;
#ifdef TESTING
f = fopen("../makedev.cfg", "r");
#else
f = fopen("/etc/makedev.cfg", "r");
#endif
if (!f) crash("Can't load /etc/makedev.cfg");
doparse(f, 4, "makedev.cfg");
fclose(f);
}
/*
* Return the index into the above table for a particular class name.
*/
static int which_class(const char *name) {
int i;
for (i=0; i<nclasses; i++)
if (!strcmp(classes[i].classname, name)) return i;
return 0;
}
/*
* Produce an "ls -l"-ish mode string.
*/
static const char *modestring(int mode) {
static char rv[12];
int i,z;
strcpy(rv, "rwxrwxrwx");
for (i=8,z=1; i>=0; i--, z<<=1) if (!(mode&z)) rv[i]='-';
return rv;
}
/*
* Create (or delete, or update) a block or character device.
*/
static void class_makedev(const char *name, const char *class,
int major, int minor, char type) {
int x = which_class(class), mode = classes[x].mode;
const char *owner = classes[x].owner, *group = classes[x].group;
if (isverbose) {
if (deletion) printf("rm -f %s\n", name);
else printf("%c%s 1 %-8s %-8s %3d, %3d for %s\n", type,
modestring(mode), owner, group, major, minor, name);
}
if (donothing) return;
if (unlink(name) && deletion) warn("Couldn't remove %s\n", name);
if (!deletion) {
dev_t q = (major<<8) | minor;
if (mknod(name, type=='c' ? S_IFCHR : S_IFBLK, q) ||
chown(name, name2uid(owner), name2gid(group)) ||
chmod(name, mode)) {
warn("couldn't create %s: %s", name, strerror(errno));
}
}
}
/************************* major number list *************************/
/*
* In Linux device major numbers can be allocated dynamically, so we go
* look in /proc/devices to see what they are. This keeps track of things.
*/
typedef struct {
const char *procname;
int flag;
} majorentry;
#define MAXMAJORS 256
static majorentry cmajors[MAXMAJORS]; /* initialized to 0 */
static majorentry bmajors[MAXMAJORS]; /* initialized to 0 */
static int no_proc=0; /* true if we didn't find /proc/devices */
/*
* Store the name associated with a particular major device number.
*/
void set_major(const char *procname, int ischar, int num) {
if (num<0 || num>255) {
warn("warning: got bogus major number %d for %s", num, procname);
return;
}
if (ischar) cmajors[num].procname=procname;
else bmajors[num].procname=procname;
}
/*
* Look up a major device number by name; return the default value
* if provided. A default value of -1 implies the device is only
* dynamic, and so if there's no entry we shouldn't even note its
* existence.
*/
int get_major(const char *procname, int ischar, int defaalt) {
int i;
if (!procname) return defaalt;
if (ischar) {
for (i=0; i<MAXMAJORS; i++)
if (cmajors[i].procname && !strcmp(cmajors[i].procname, procname))
return i;
}
else {
for (i=0; i<MAXMAJORS; i++)
if (bmajors[i].procname && !strcmp(bmajors[i].procname, procname))
return i;
}
return defaalt;
}
/*
* Read /proc/devices.
*/
static void setup_majors(void) {
FILE *f = fopen("/proc/devices", "r");
if (!f) {
fprintf(stderr, "MAKEDEV: warning: can't read /proc/devices\n");
no_proc = 1;
return;
}
doparse(f, 1, "/proc/devices");
fclose(f);
}
/************************** procname list *************************/
/*
* The names found in /proc/devices aren't usually quite the same
* as the names we use. This is a mapping between the two namespaces.
*/
typedef struct {
const char *procname;
const char *groupname;
} namealias;
#define MAXALIASES 100
static namealias aliases[MAXALIASES];
static int naliases=0;
void addalias(const char *procname, const char *groupname) {
if (naliases>=MAXALIASES) crash("out of space for aliases");
aliases[naliases].procname = procname;
aliases[naliases].groupname = groupname;
naliases++;
}
void ignore_procname(const char *procname) {
addalias(procname, NULL);
}
static const char *procnameof(const char *groupname) {
int i;
for (i=0; i<naliases; i++) if (!strcmp(groupname, aliases[i].groupname))
return aliases[i].procname;
return NULL;
}
static const char *groupnameof(const char *procname) {
int i;
for (i=0; i<naliases; i++) if (!strcmp(procname, aliases[i].procname))
return aliases[i].groupname;
return NULL;
}
/************************* batch list *************************/
/*
* Create a device "batch" - a bunch of devices or groups.
* This is used for "generic" and automatically for disk entries.
* (Disk entries for "hd" come up with groups hda, hdb, etc., but
* "hd" itself needs to run these too.)
*/
#define MAXBATCHES 16
/* batch structure is in devices.h */
static batch batches[MAXBATCHES];
static int nbatches=0;
/*
* Start a new batch.
*/
batch *addbatch(const char *name) {
batch *b;
if (nbatches>=MAXBATCHES) crash("Out of space for batches");
b = &batches[nbatches++];
b->name = name;
b->busy = NO;
return b;
}
/*
* Add something to a batch.
*/
batch *add2batch(batch *b, const char *target) {
if (b->ntargets>=MAXTARGETS) {
warn("Too many targets for batch %s (max %d)", b->name, MAXTARGETS);
return b;
}
b->targets[b->ntargets++] = target;
return b;
}
/*
* Run a batch.
*/
static void run_batch(const batch *b, makeopts m) {
int i;
for (i=0; i<b->ntargets; i++) make(b->targets[i], m);
}
/*
* Try to run a batch; returns YES if it found one.
*/
static int try_run_batch(const char *name, makeopts m) {
int i;
for (i=0; i<nbatches; i++) {
if (!strcmp(name, batches[i].name)) {
if (batches[i].busy) {
warn("Found recursive batch definition for %s", batches[i].name);
continue;
}
batches[i].busy=YES;
run_batch(&batches[i], m);
batches[i].busy=NO;
return YES;
}
}
return NO;
}
/************************* device list *************************/
/*
* Structure to remember the properties of an individual device.
* NOTE: if the device is actually a symbolic link, the "class"
* member is used to store the thing it should be linked to.
*/
typedef struct {
const char *name; /* file name to create */
const char *grp; /* device "group" name (e.g. "busmice") */
const char *class; /* device class ( -> owner and permissions) */
int major, minor; /* device number */
char type; /* 'c', 'b', or 'l' for symbolic link */
int omit; /* don't make me if this is nonzero */
} device;
/*
* Create a device (link or actual "special file") - special files are
* passed on to class_makedev().
*/
void makedev(device *d, makeopts m) {
if (m==M_OMIT) {
d->omit=1;
}
if (d->omit==1) return;
if (d->type=='l') {
if (isverbose) {
if (deletion) printf("rm -f %s\n", d->name);
else printf("lrwxrwxrwx %s -> %s\n", d->name, d->class);
}
if (donothing) return;
if (unlink(d->name) && deletion) warn("Couldn't remove %s\n", d->name);
if (!deletion) {
if (symlink(d->class, d->name)) /* class holds thing pointed to */
warn("couldn't link %s -> %s: %s", d->name, d->class, strerror(errno));
}
}
else class_makedev(d->name, d->class, d->major, d->minor, d->type);
}
/*
* Array of devices. We allocate it once from main(); it doesn't grow.
* Should maybe make it growable sometime. This keeps track of all possible
* devices. We build this thing first, and then create devices from it as
* requested.
*/
static device *devices = NULL;
static int maxdevices, ndevices;
/*
* Allocate space for the device array.
*/
static void allocate_devs(int nd) {
devices = malloc(nd * sizeof(device));
if (!devices) crash("Out of memory");
ndevices = 0;
maxdevices = nd;
}
/*
* Check all the devices for having valid device classes.
*/
static void check_classes(void) {
int i;
const char *q=NULL;
for (i=0; i<ndevices; i++)
if (devices[i].type!='l' && !devices[i].omit &&
which_class(devices[i].class)<0) {
if (!q || strcmp(q, devices[i].class)) {
warn("Invalid device class %s for %s",
devices[i].class, devices[i].name);
q = devices[i].class;
}
devices[i].class = "default";
}
}
/*
* Create an entry in the device table for a single device.
*/
void init(const char *name, const char *grp, const char *class,
int major, int minor, int type) {
if (major < 0) return;
if (!strchr("bcl", type)) {
warn("invalid device type %c for %s (skipping)", type, name);
return;
}
if (ndevices>=maxdevices) crash("out of space for devices");
devices[ndevices].name = name;
devices[ndevices].grp = grp;
devices[ndevices].class = class;
devices[ndevices].major = major;
devices[ndevices].minor = minor;
devices[ndevices].type = type;
devices[ndevices].omit = 0;
ndevices++;
}
/*
* Create an entry for a symbolic link "device", such as /dev/fd
* (which is a symbolic link to /proc/self/fd)
*/
void initlink(const char *name, const char *grp, const char *target) {
init(name, grp, target, 0, 0, 'l');
}
/*
* Init lots of devices. This creates a number of devices, numbered between
* lo and hi. The idea is that "base" contains a %d or %x (or something like
* that) in it which pulls in the number. The device group can also do this,
* though this will in most cases not be useful. "baseminor" is the minor
* number of the first device created.
*/
void initlots(const char *base, int lo, int hi, const char *grp,
const char *class,
int maj, int baseminor, int type) {
char buf[32], gbuf[32];
int i;
if (maj<0) return;
for (i=lo; i<=hi; i++) {
sprintf(buf, base, i);
if (grp) sprintf(gbuf, grp, i); /* grp is permitted to contain a %d */
init(strdup(buf), grp ? strdup(gbuf) : NULL, class,
maj, baseminor+i-lo, type);
}
}
/*
* Init a whole (hard) disk's worth of devices - given `hd', it makes
* hda1...hda8 through hdd1...hdd8 in one fell swoop. "low" and "high"
* are the letters to use ('a' and 'd' for the previous example).
* "nparts" is the number of partitions to create, ordinarily 8.
* "maj" is the major device number; minmult is the multiplier for the
* minor number. That is, if hda starts at 0, and hdb starts at 64, minmult
* is 64.
*
* Note that it creates "hda", "hdb", etc. too, and puts things in the
* groups "hda", "hdb", etc. as appropriate. The class is set to "disk".
*/
void initdisk(const char *base, int low, int high, int nparts,
int maj, int minmult) {
char buf[16], buf2[16];
int i;
batch *b;
if (maj<0) return;
if (low>=high) return;
b = addbatch(base);
for (i=low; i<=high; i++) {
char *q;
sprintf(buf, "%s%c", base, i);
q = strdup(buf);
init(q, q, "disk", maj, (i-low)*minmult, 'b');
strcpy(buf2, buf);
strcat(buf2, "%d");
initlots(buf2, 1, nparts, buf, "disk", maj, (i-low)*minmult+1, 'b');
add2batch(b, q);
}
}
static void initdevs(void) {
FILE *f;
#ifdef TESTING
f = fopen("../devinfo", "r");
#else
f = fopen("/etc/devinfo", "r");
#endif
if (!f) crash("Can't read /etc/devinfo");
doparse(f,3, "devinfo");
fclose(f);
f = fopen("/usr/local/etc/devinfo.local", "r");
if (!f) f = fopen("/etc/devinfo.local", "r");
if (f) {
doparse(f,3, "devinfo.local");
fclose(f);
}
}
/************************** update *************************/
/*
* Call make() with our names for something that appeared in /proc/devices.
*/
static void transmake(const char *procname, makeopts m) {
const char *gname = groupnameof(procname);
if (gname) make(gname, m);
}
/*
* Update a device that appeared in MAKEDEV.cache. Whenever we update,
* we save what we did into MAKEDEV.cache; this lets us avoid doing
* them over the next time. We only do something if the device has
* disappeared or the major number has changed.
*
* Note that this caching made the shell version go much faster (it took
* around 15 seconds with the cache, vs. over a minute if the cache was
* blown away.) For us, it still does, but it hardly matters: it shaves
* one second off a two-second execution.
*
* Also note the old script used DEVICES instead of MAKEDEV.cache. We
* changed because the old file didn't record whether something was
* a block or character device; since the sets of numbers are independent,
* this was bound to break.
*/
static void update2(const char *name, int ischar, int major) {
int now = get_major(name, ischar, -1);
if (now<0) {
deletion = 1; /* must have been zero if we're doing an update */
transmake(name, M_CREATE);
deletion = 0;
}
else if (now!=major) { /* oops, it moved; remake it */
transmake(name, M_CREATE);
if (ischar) cmajors[now].flag=1;
else bmajors[now].flag=1;
}
else {
if (ischar) cmajors[now].flag=1; /* unchanged; inhibit remaking it */
else bmajors[now].flag=1; /* unchanged; inhibit remaking it */
}
}
void updatefromcache(const char *name, int major, int type) {
update2(name, type=='c', major);
}
/*
* Update. Read the information stored in MAKEDEV.cache from the last
* update; fix anything that changed; then create any new devices that
* weren't listed the last time. (We use the "flag" field in the
* majors array to check this.) At that point, write out a new
* cache file.
*/
#define CACHEFILE "MAKEDEV.cache"
static void update(void) {
FILE *f;
int i;
if (no_proc) { warn("Couldn't read anything from /proc/devices"); return; }
if (deletion) { warn("update and -d are incompatible"); return; }
f = fopen(CACHEFILE, "r");
if (f) {
doparse(f, 2, CACHEFILE);
fclose(f);
}
for (i=0; i<MAXMAJORS; i++) {
if (cmajors[i].procname && !cmajors[i].flag) {
transmake(cmajors[i].procname, M_CREATE);
cmajors[i].flag=1;
}
if (bmajors[i].procname && !bmajors[i].flag) {
transmake(bmajors[i].procname, M_CREATE);
bmajors[i].flag=1;
}
}
if (donothing) return;
f = fopen(CACHEFILE, "w");
if (f) {
for (i=0; i<MAXMAJORS; i++) {
if (cmajors[i].procname) fprintf(f, "%s %d char\n", cmajors[i].procname, i);
if (bmajors[i].procname) fprintf(f, "%s %d block\n", bmajors[i].procname, i);
}
fclose(f);
}
else warn("warning: can't write MAKEDEV.cache");
}
/************************* work *************************/
/*
* Create (or delete, etc. according to flags) a device or device group.
* The "generic" group is handled specially by recursing once.
* "update" is handled specially; see update() below.
* "local" issues a warning; people should use DEVINFO.local instead.
*/
void make(const char *what, makeopts m) {
int i;
if (!strcmp(what, "update")) {
if (m!=M_CREATE) warn("update not compatible with those options");
else update();
}
else if (!strcmp(what, "local")) {
warn("The local target is obsolete.");
}
else if (!try_run_batch(what, m)) {
int found=0;
for (i=0; i<ndevices; i++) {
if ((devices[i].grp && !strcmp(what, devices[i].grp)) ||
!strcmp(what, devices[i].name)) {
makedev(&devices[i], m);
found = 1;
}
}
if (!found) warn("unknown device or device group %s", what);
}
}
/*
* A major improvement over the shell version...
*/
static void usage(void) {
printf("%s usage:\n", MYNAME);
printf(" %s [-vdcn] device [device...]\n", MYNAME);
printf(" -v Verbose output\n");
printf(" -d Remove specified devices\n");
printf(" -c Create devices (default)\n");
printf(" -n Don't actually do anything (implies -v)\n");
printf(" -I Immediate; work in current directory, not /dev\n");
printf(" -V Print version information\n");
printf("\n");
}
static void setcwd(void) {
char buf[64];
if (!getcwd(buf, sizeof(buf)) || strcmp(buf, "/dev")) {
printf("Notice: changing my directory to /dev.\n");
chdir("/dev");
}
}
/*
* We should use getopt one of these days.
*/
int main(int argc, char **argv) {
int i,j, done=0;
int immediate;
#ifdef TESTING
immediate=1; /* you don't think I run buggy versions in /dev, do you? */
#else
immediate=0;
#endif
for (i=1; i<argc && argv[i][0]=='-' && !done; i++) {
for (j=1; argv[i][j] && !done; j++) switch(argv[i][j]) {
case '-': done=1; break;
case 'v': isverbose = 1; break;
case 'd': deletion = 1; break;
case 'c': deletion = 0; break;
case 'n': donothing = 1; isverbose = 1; break;
case 'h': usage(); exit(0);
case 'I': immediate = 1; break;
case 'V': printf("MAKEDEV: %s\n", version); exit(0);
default: fprintf(stderr, "%s: unknown flag %c\n", MYNAME, argv[i][j]);
exit(1);
}
}
if (!immediate) setcwd(); /* go into /dev */
setup_majors(); /* read major device numbers from /proc */
allocate_devs(1500); /* make space to hold devices */
initdevs(); /* set up device structures */
loadclasses(); /* load device classes from config file */
check_classes(); /* make sure no devices have bogus classes */
if (i==argc) warn("didn't do anything; try -h for help.");
else for (; i<argc; i++) make(argv[i], M_CREATE);
return 0;
}